Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 15x 15x 15x 6x 6x 6x 5x 6x 6x 6x 6x 6x 11x 11x 11x 10x 10x 11x 11x 11x 11x 11x | import path from 'node:path';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { MarkdownPage } from '@/components/MarkdownPage';
import { getAllContentFiles, getContentBySlug } from '@/lib/content';
import {
getAdjacentPages,
getBreadcrumbs,
getCategoryBySlug,
getPageByUrl,
getTabContext,
} from '@/lib/navigation';
export const dynamicParams = false;
type RouteParams = { category: string; slug?: string[] };
/**
* Static-export catch-all: emits one route per `content/**\/*.md(x)` file,
* skipping content-root files (home.md, navigation.md) which are handled
* elsewhere. Only paths returned here are built — `dynamicParams = false`
* makes any unlisted request 404 at runtime.
*/
export async function generateStaticParams(): Promise<RouteParams[]> {
const contentDir = path.resolve(process.cwd(), 'content');
const params: RouteParams[] = [];
const seen = new Set<string>();
for (const filePath of getAllContentFiles(contentDir)) {
const rel = path.relative(contentDir, filePath).replace(/\\/g, '/');
const withoutExt = rel.replace(/\.mdx?$/, '');
const segments = withoutExt.split('/');
Iif (segments.length < 2) continue;
const [category, ...rest] = segments;
const key = `${category}/${rest.join('/')}`;
Iif (seen.has(key)) continue;
seen.add(key);
params.push({ category, slug: rest });
}
return params;
}
/** The route's URL, the nav page it belongs to, and that page's tab strip (if any). */
function resolveNav(category: string, slug: string[] | undefined) {
const url = `/${[category, ...(slug ?? [])].join('/')}`;
const navPage = getPageByUrl(url);
return { url, navPage, tabContext: navPage ? getTabContext(navPage, url) : null };
}
export async function generateMetadata({ params }: { params: Promise<RouteParams> }): Promise<Metadata> {
const { category, slug } = await params;
const content = await getContentBySlug(category, slug);
if (!content) return {};
const rawDescription = content.metadata.description ?? content.metadata.tagline;
const description = typeof rawDescription === 'string' ? rawDescription : undefined;
// A tab page is titled "<parent> - <tab>" so browser tabs and search results
// distinguish "Badge - Usage" from "Badge - Accessibility". The parent's nav
// name is used because tab front-matter titles already carry the tab name.
const { tabContext } = resolveNav(category, slug);
const activeTab = tabContext?.tabs.find((t) => t.active);
const titleBase =
activeTab && tabContext && activeTab.url !== tabContext.parent.url
? `${tabContext.parent.name} - ${activeTab.name}`
: content.metadata.title;
return {
title: `${titleBase} | SLDS 2`,
description,
};
}
export default async function ContentPage({ params }: { params: Promise<RouteParams> }) {
const { category, slug } = await params;
const content = await getContentBySlug(category, slug);
if (!content) notFound();
const { url, navPage, tabContext } = resolveNav(category, slug);
const breadcrumbs = navPage ? getBreadcrumbs(navPage) : [];
// Tab pages share their parent's place in the nav, so the pager and landing
// check both run against the parent page.
const { prev, next } = navPage ? getAdjacentPages(navPage) : { prev: null, next: null };
// A category's first child is its landing page (top level); everything deeper is second level.
const navCategory = navPage ? getCategoryBySlug(navPage.category ?? category) : undefined;
const isTopLevel = !!navPage && navCategory?.children[0]?.url === navPage.url;
// The subtitle shows on the parent page only, not on its other tabs.
const description =
typeof content.metadata.description === 'string' && url === (navPage?.url ?? url.split('--')[0])
? content.metadata.description
: undefined;
return (
<MarkdownPage
// Tabs share the parent's heading (Zeroheight / POC); the tab strip names the tab.
frontmatter={{ title: tabContext?.parent.name ?? content.metadata.title }}
html={content.html}
tabs={tabContext?.tabs}
breadcrumbs={breadcrumbs}
markdown={content.rawMarkdown}
description={description}
prev={prev && { name: prev.name, url: prev.url }}
next={next && { name: next.name, url: next.url }}
isTopLevel={isTopLevel}
/>
);
}
|